﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ArithmeticLibrary.Tree;

namespace ArithmeticLibrary.Visitors
{
    /// <summary>
    /// Visitor pro tisk vyrazu v infixovem tvaru (minimalni pocet zavorek).
    /// </summary>
    class InfixPriorityPrinterVisitor : IExpressionVisitor<EmptyResult, NoContext>
    {
        /// <summary>
        /// Dictionary na priority.
        /// </summary>
        private static Dictionary<Type, int> priorities = new Dictionary<Type, int>();
        private StringBuilder expression;

        /// <summary>
        /// Nastavi priority operaci.
        /// </summary>
        static InfixPriorityPrinterVisitor()
        {
            priorities.Add(typeof(PlusNode), 1);
            priorities.Add(typeof(MinusNode), 1);
            priorities.Add(typeof(MultiplyNode), 2);
            priorities.Add(typeof(DivideNode), 2);
            priorities.Add(typeof(PowerNode), 3);
            priorities.Add(typeof(SineNode), 4);
            priorities.Add(typeof(CosineNode), 4);
            priorities.Add(typeof(TangentNode), 4);
            priorities.Add(typeof(ExponentialNode), 4);
            priorities.Add(typeof(LogarithmNode), 4);
            priorities.Add(typeof(UnaryMinusNode), 4);
            priorities.Add(typeof(VariableNode), 10);
            priorities.Add(typeof(NumberNode), 10);
        }

        /// <summary>
        /// Zjisti, jestli potrebujeme zavorky.
        /// </summary>
        private static bool NeedsParentheses(BinaryOperatorNode parentOp, Node child)
        {
            return priorities[parentOp.GetType()] > priorities[child.GetType()];
        }

        private static bool NeedsParenthesesRightOp(BinaryOperatorNode parentOp, Node child)
        {
            return priorities[parentOp.GetType()] >= priorities[child.GetType()];
        }

        /// <summary>
        /// Navstivi operand.
        /// </summary>
        private void VisitOperand(Node node, bool addParentheses)
        {
            if (addParentheses) expression.Append('(');
            node.Accept(this);
            if (addParentheses) expression.Append(')');
        }

        private void VisitOperand(Node node)
        {
            expression.Append('(');
            node.Accept(this);
            expression.Append(')');
        }

        /// <summary>
        /// Visit pro vnitrni volani (binarni operatory).
        /// </summary>
        private void InternalVisit(BinaryOperatorNode node, string operatorSign, bool leftAssociative)
        {
            VisitOperand(node.Left, NeedsParentheses(node, node.Left));
            expression.Append(operatorSign);
            VisitOperand(node.Right, leftAssociative ? NeedsParentheses(node, node.Right) : NeedsParenthesesRightOp(node, node.Right));
        }

        /// <summary>
        /// Visit pro vnitrni volani (unarni operatory).
        /// </summary>
        private void InternalVisit(UnaryOperatorNode node, string operatorSign, bool leftAssiciative)
        {
            expression.Append(operatorSign);
            VisitOperand(node.Child);
        }


        public EmptyResult Visit(NumberNode node, NoContext context)
        {
            expression.Append(node.Value);
            return null;
        }

        public EmptyResult Visit(VariableNode node, NoContext context)
        {
            expression.Append(node.Name);
            return null;
        }

        public EmptyResult Visit(UnaryMinusNode node, NoContext context)
        {
            InternalVisit(node, "-", true);
            return null;
        }

        public EmptyResult Visit(CosineNode node, NoContext context)
        {
            InternalVisit(node, "cos", true);
            return null;
        }

        public EmptyResult Visit(SineNode node, NoContext context)
        {
            InternalVisit(node, "sin", true);
            return null;
        }

        public EmptyResult Visit(TangentNode node, NoContext context)
        {
            InternalVisit(node, "tan", true);
            return null;
        }

        public EmptyResult Visit(ExponentialNode node, NoContext context)
        {
            InternalVisit(node, "exp", true);
            return null;
        }

        public EmptyResult Visit(LogarithmNode node, NoContext context)
        {
            InternalVisit(node, "log", true);
            return null;
        }

        public EmptyResult Visit(PlusNode node, NoContext context)
        {
            InternalVisit(node, "+", true);
            return null;
        }

        public EmptyResult Visit(MinusNode node, NoContext context)
        {
            InternalVisit(node, "-", false);
            return null;
        }

        public EmptyResult Visit(MultiplyNode node, NoContext context)
        {
            InternalVisit(node, "*", true);
            return null;
        }

        public EmptyResult Visit(DivideNode node, NoContext context)
        {
            InternalVisit(node, "/", false);
            return null;
        }

        public EmptyResult Visit(PowerNode node, NoContext context)
        {
            InternalVisit(node, "^", true);
            return null;
        }

        public string ExpressionToString(Node node)
        {
            expression = new StringBuilder();
            node.Accept(this);
            return expression.ToString();
        }
    }


}
